using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Timers;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using SharedObjects;

namespace TheGame.Server
{
	public class Tracer
	{
		public event EventHandler<TracerEventArgs> TraceStarted;
		public event EventHandler<TracerEventArgs> TraceUpdated;
		public event EventHandler<TracerEventArgs> TraceEnded;

		private readonly IObjectContainer _db;
		private readonly ReadOnlyCollection<ulong> _hops;
		private readonly Timer _traceUpdateTimer;
		private readonly List<int> _baseHopTimes = new List<int>
		{
			100,	//sl0
			20000,	//sl1
			15000,	//sl2
			12500,	//sl3
			10000,	//sl4
			7500,	//sl5
			5000,	//sl6
			3750,	//sl7
			2500,	//sl8
			1250,	//sl9
			625,	//sl10
		};

		private const double DiminishingExponent = 0.7461;
		private int _currentHop;

		private readonly List<double> _hopTimes = new List<double>();
		private readonly Dictionary<ulong, int> _secLevels = new Dictionary<ulong, int>();
		
		public Tracer(IObjectContainer database, List<ulong> hoplist)
		{
			_db = database;
			_hops = hoplist.AsReadOnly();
			var systems = hoplist.Select(id => (from ComputerSystem cs in _db where cs.Id == id select cs).First());
			foreach (var x in systems)
				_secLevels.Add(x.Id, x.DifficultyLevel);

			for (int i = 0; i < _hops.Count; i++)
			{
				_hopTimes.Add(DiminishHopTime(i));
			}
			_traceUpdateTimer = new Timer(_hopTimes[_currentHop]);
			_traceUpdateTimer.AutoReset = false;
			_traceUpdateTimer.Elapsed += TraceUpdateTimerElapsed;

		}

		public void Start()
		{
			_traceUpdateTimer.Start();
			if (TraceStarted != null)
				TraceStarted(this, new TracerEventArgs { RemainingHops = _hops.Count, RemainingMilliseconds = GetRemainingTime()});
		}

		private void TraceUpdateTimerElapsed(object sender, ElapsedEventArgs e)
		{
			_currentHop++;
			_traceUpdateTimer.Stop();
			if (_currentHop >= _hops.Count)
			{
				if (TraceEnded != null)
					TraceEnded(this, null);
				return;
			}
			//if hop times are diminished, update timer interval
			double newHopTime = _hopTimes[_currentHop];
			if (newHopTime == 0)
			{
				TraceUpdateTimerElapsed(this, null);
				return;
			}
			_traceUpdateTimer.Interval = newHopTime;
			if (TraceUpdated != null)
				TraceUpdated(this, new TracerEventArgs { RemainingHops = _hops.Count - _currentHop, RemainingMilliseconds = GetRemainingTime() });
			_traceUpdateTimer.Start();
		}

		private int GetRemainingTime()
		{
			double totalTime;
			totalTime = _hopTimes.GetRange(_currentHop, _hopTimes.Count - _currentHop).Sum();
			return (int)totalTime;
		}

		private int DiminishHopTime(int hopNum)
		{
			ulong sysId = _hops[hopNum];
			int difficulty = _secLevels[sysId];
			double mseconds = _baseHopTimes[difficulty] * 5;

			double result = mseconds/Math.Pow(hopNum+1, DiminishingExponent);
			return (int)Math.Round(result);
		}
	}

	public class TracerEventArgs : EventArgs
	{
		public int RemainingMilliseconds { get; set; }
		public int RemainingHops { get; set; }
	}
}